#pragma once

#include<type_traits>
#include<cstddef>
#include<iterator>

namespace hamster
{
    template<typename T>
    struct hamster_type_traits
    {
        using value_type = std::conditional_t<
                           std::is_pointer_v<T>,
                           std::remove_cv_t<std::remove_pointer_t<T>>,
                           std::remove_cv_t<std::remove_reference_t<T>>
                           >;
        
        using pointer_type = value_type*;
        using const_pointer_type = const value_type*;

        using lvalue_reference_type = value_type&;
        using const_lvalue_reference_type = const value_type&;

        using rvalue_reference_type = value_type&&;

    };

    // iterator type tag -> class type tag
    // just for show, not used in this code, use std::iterrator_tag instead
    struct input_iterator_tag {};
    struct output_iterator_tag {};
    struct forward_iterator_tag : public input_iterator_tag {};
    struct bidirectional_iterator_tag : public forward_iterator_tag {};
    struct random_access_iterator_tag : public bidirectional_iterator_tag {};

    // for iterator type traits
    template<typename T>
    struct hamster_iterator_traits
    {
        using value_type        = typename T::value_type;
        using pointer           = typename T::pointer;
        using reference         = typename T::reference;
        // class type iterator
        using difference_type   = typename T::difference_type;
        using iterator_category = typename T::iterator_category;
    };

    // partial specialization for pointer type
    template<typename T>
    struct hamster_iterator_traits<T*>
    {
        using value_type        = T;
        using pointer           = T*;
        using reference         = T&;
        using difference_type   = std::ptrdiff_t;
        using iterator_category = std::random_access_iterator_tag;
    };

    // partial specialization for const pointer type
    template<typename T>
    struct hamster_iterator_traits<const T*>
    {
        using value_type        = T;
        using pointer           = const T*;
        using reference         = const T&;
        using difference_type   = std::ptrdiff_t;
        using iterator_category = std::random_access_iterator_tag;
    };
    
    template<typename T>
    typename hamster_iterator_traits<T>::iterator_category
    iterator_category(const T&) noexcept
    {
        using category = hamster_iterator_traits<T>::iterator_category;
        return category();
    }

    // get a difference_type
    template<typename T>
    inline
    typename hamster_iterator_traits<T>::difference_type*
    distance_type(const T&) noexcept
    {
        using difference_type = hamster_iterator_traits<T>::difference_type;
        return static_cast<difference_type*>(nullptr);
    }

    // get a value type
    template<typename T>
    inline
    typename hamster_iterator_traits<T>::value_type*
    value_type(const T&) noexcept
    {
        using value_type = hamster_iterator_traits<T>::value_type;
        return static_cast<value_type*>(nullptr);
    }

    template<typename Iterator, typename Distance>
    inline void advance(Iterator& iter, Distance n, std::input_iterator_tag)
    {
        while(n--) ++iter;
    }

    template<typename Iterator, typename Distance>
    inline void advance(Iterator& iter, Distance n, std::bidirectional_iterator_tag)
    {
        if(n >= 0)
        {
            while(n--) ++iter;
        }
        else
        {
            while(n++) --iter;
        }
    }

    template<typename Iterator, typename Distance>
    inline void advance(Iterator& iter, Distance n, std::random_access_iterator_tag)
    {
        iter += n;
    }

    // algorithm advance()
    template<typename Iterator, typename Distance>
    inline void advance(Iterator& iter, Distance n)
    {
        advance(iter, n, iterator_category(iter));
    }

    template<typename Iterator>
    inline 
    typename hamster_iterator_traits<Iterator>::difference_type
    Distance(Iterator first, Iterator last, std::input_iterator_tag)
    {
        using difference_type = hamster_iterator_traits<Iterator>::difference_type;
        difference_type n = 0;
        while(first != last)
        {
            ++first;
            ++n;
        }
        return n;
    }

    template<typename Iterator>
    inline
    typename hamster_iterator_traits<Iterator>::difference_type
    Distance(Iterator first, Iterator last, std::random_access_iterator_tag)
    {
        return last - first;
    }

    template<typename Iterator>
    inline
    typename hamster_iterator_traits<Iterator>::difference_type
    Distance(Iterator first, Iterator last)
    {
        return Distance(first, last, iterator_category(first));
    }

};

/*
using :
     // Assume we don't know a vector<double> element type
    std::vector<double> vec;

    // Using my_iterator_traits to deduce the element type of the vector
    std::cout << "vector element type: "
    << hamster::demangle<my_iterator_traits<decltype(*vec.begin())>::value_type>() << std::endl;
*/
